package com.icekrvams.billing

import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClient.BillingResponseCode
import com.android.billingclient.api.BillingClient.ProductType
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.ProductDetailsResult
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.Purchase.PurchaseState
import com.android.billingclient.api.PurchasesResult
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryProductDetailsParams.Product
import com.android.billingclient.api.QueryPurchasesParams
import com.android.billingclient.api.acknowledgePurchase
import com.android.billingclient.api.consumePurchase
import com.android.billingclient.api.queryProductDetails
import com.android.billingclient.api.queryPurchasesAsync
import com.icekrvams.billing.data.DataHolder
import com.icekrvams.billing.data.consumablePurchases
import com.icekrvams.billing.data.oneTimePurchases
import com.icekrvams.billing.data.subPurchases
import com.icekrvams.billing.listeners.ListenerHolder
import com.icekrvams.billing.utilities.BillingUtilities
import com.icekrvams.billing.utilities.dLog
import com.icekrvams.billing.utilities.eLog
import com.icekrvams.billing.utilities.iLog
import com.icekrvams.billing.utilities.resetWith
import com.icekrvams.billing.utilities.wtfLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

private fun String.isSub(): Boolean {
    return this == ProductType.SUBS
}

internal object BillingCore : PurchasesUpdatedListener, BillingClientStateListener {

    private val workCoroutineScope = CoroutineScope(Dispatchers.IO)

    /**
     * 维护 BillingClient 对象。
     * Billing 的场景比较复杂且多变，未避免考虑不周，Billing 组件将其维护的 billingClient 对象暴露出来，
     * 以便开发者对其具有更高的控制权。然而在非必要的情况下，仍不推荐直接引用。
     */
    lateinit var billingClient: BillingClient
        private set

    /**
     * BillingClient 对象已经被初始化，且 billingClient 已建立连接
     */
    internal fun isBillingClientReady(): Boolean {
        return this::billingClient.isInitialized && billingClient.isReady
    }

    /**
     * 确保初始化并连接 BillingClient，允许重复调用，但不会重复建立连接。
     */
    fun ensureBillingClientConnection() {
        if (!isBillingClientReady()) {
            billingClient = BillingClient.newBuilder(Billing.app)
                .enablePendingPurchases()
                .setListener(this)
                .build()
            "BillingClient: Start connection...".dLog()
            billingClient.startConnection(this)
        }
    }

    /**
     * 断开 BillingClient 连接。
     */
    fun endConnection() {
        if (billingClient.isReady) {
            "BillingClient can only be used once -- closing connection".dLog()
            billingClient.endConnection()
        }
    }

    override fun onBillingSetupFinished(billingResult: BillingResult) {
        val responseCode = billingResult.responseCode
        val debugMessage = billingResult.debugMessage
        "onBillingSetupFinished: $responseCode $debugMessage".dLog()
        /*即便不满足 responseCode == BillingClient.BillingResponseCode.OK 的条件，仍然进行商品和购买项查询。
        已发现 Case：PlayStore 退出登陆后，responseCode == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE，
        此时仍需要进行查询，避免退出登陆后，保持已缓存的已订阅状态的情况。 */
        workCoroutineScope.launch {
            launch {
                queryProductDetails()
            }
            launch {
                queryPurchases()
            }
        }
    }

    override fun onBillingServiceDisconnected() {
        // To do somethings about retrying.
    }

    /**
     * 查询商品详情，商品详情用于启动购买流程。
     */
    internal suspend fun queryProductDetails(): ProductDetailsResult {
        return withContext(Dispatchers.IO) {
            "Querying product details...".dLog()
            val subProductDetailsResultDeffer = async {
                if (DataHolder.products.availableSubProducts.isNotEmpty()) {
                    queryProductDetailsByType(ProductType.SUBS)
                } else {
                    BillingUtilities.generateEmptyProductDetailsResult(ProductType.SUBS)
                }
            }
            val iapProductDetailsResultDeffer = async {
                if (DataHolder.products.allAvailableIapProducts.isNotEmpty()) {
                    queryProductDetailsByType(ProductType.INAPP)
                } else {
                    BillingUtilities.generateEmptyProductDetailsResult(ProductType.INAPP)
                }
            }
            val subProductDetailsResult = subProductDetailsResultDeffer.await()
            val iapProductDetailsResult = iapProductDetailsResultDeffer.await()
            // 合并订阅商品和内购商品信息的查询结果。
            val billingResult = combineBillingResult(
                subProductDetailsResult.billingResult,
                iapProductDetailsResult.billingResult
            )
            val productDetailsList = (
                subProductDetailsResult.productDetailsList
                    ?: emptyList()
                ) + (iapProductDetailsResult.productDetailsList ?: emptyList())
            handleProductDetailsQueryResult(billingResult, productDetailsList)
            ProductDetailsResult(billingResult, productDetailsList)
        }
    }

    /**
     * 根据类型查询商品信息。
     * @param type 商品类型。
     */
    private suspend fun queryProductDetailsByType(@ProductType type: String): ProductDetailsResult {
        val productList = when (type) {
            ProductType.SUBS -> DataHolder.products.availableSubProducts
            ProductType.INAPP -> DataHolder.products.allAvailableIapProducts
            else -> throw IllegalStateException("Invalid product type")
        }
        val params = QueryProductDetailsParams.newBuilder().setProductList(
            productList.map {
                Product.newBuilder().setProductId(it).setProductType(type).build()
            }
        )
        return if (this::billingClient.isInitialized) billingClient.queryProductDetails(params.build()) else ProductDetailsResult(
            BillingResult.newBuilder().setResponseCode(BillingResponseCode.ERROR)
                .setDebugMessage("BillingClient has not been initialized.").build(), null
        )
    }

    /**
     * 合并 sub 和 iap 的查询结果。
     * @param subBillingResult 订阅商品信息查询结果。
     * @param iapBillingResult 内购商品信息查询结果。
     */
    private fun combineBillingResult(
        subBillingResult: BillingResult,
        iapBillingResult: BillingResult
    ): BillingResult {
        val billingResultCode = when {
            subBillingResult.responseCode == iapBillingResult.responseCode -> subBillingResult.responseCode
            subBillingResult.responseCode == BillingResponseCode.OK || iapBillingResult.responseCode == BillingResponseCode.OK -> BillingResponseCode.OK
            else -> BillingResponseCode.ERROR
        }
        val debugMessage = if (subBillingResult.debugMessage == iapBillingResult.debugMessage) {
            subBillingResult.debugMessage
        } else {
            "sub:${subBillingResult.debugMessage} iap:${iapBillingResult.debugMessage}"
        }
        return BillingResult.newBuilder().setResponseCode(billingResultCode)
            .setDebugMessage(debugMessage).build()
    }

    /**
     * 处理商品信息查询结果。
     * @param billingResult 商品信息查询结果。
     * @param productDetailsList 商品信息列表。
     */
    private suspend fun handleProductDetailsQueryResult(
        billingResult: BillingResult,
        productDetailsList: List<ProductDetails>
    ) {
        ListenerHolder.onQueryProductDetailsResponse(billingResult, productDetailsList)
        val responseCode = billingResult.responseCode
        val debugMessage = billingResult.debugMessage
        when (responseCode) {
            BillingResponseCode.OK -> {
                "onProductDetailsResponse: ${BillingResponseCode.OK} $debugMessage".iLog()
                val productWithProductDetailsMap = productDetailsList.associateBy {
                    it.productId
                }
                DataHolder.productWithProductDetails.resetWith(productWithProductDetailsMap)
                val productDetailsCount = productWithProductDetailsMap.size
                val expectedProductDetailsCount = DataHolder.products.allAvailableProducts.size
                if (productDetailsCount == expectedProductDetailsCount) {
                    "onProductDetailsResponse: Found $productDetailsCount ProductDetails.".dLog()
                } else {
                    """
                        onProductDetailsResponse: Expected $expectedProductDetailsCount, Found $productDetailsCount ProductDetails.
                        Check to see if the Products you requested are correctly published in the Google Play Console.
                    """.trimIndent().eLog()
                }
            }
            BillingResponseCode.SERVICE_DISCONNECTED,
            BillingResponseCode.SERVICE_UNAVAILABLE,
            BillingResponseCode.BILLING_UNAVAILABLE,
            BillingResponseCode.DEVELOPER_ERROR,
            BillingResponseCode.ERROR,
            BillingResponseCode.FEATURE_NOT_SUPPORTED
            -> {
                "onProductDetailsResponse: $responseCode $debugMessage".eLog()
            }
            else -> {
                "onProductDetailsResponse: $responseCode $debugMessage".wtfLog()
            }
        }
    }

    /**
     * 查询已拥有的购买项。
     */
    internal suspend fun queryPurchases() {
        if (!billingClient.isReady) {
            "QueryPurchases: BillingClient is not ready".eLog()
        }
        "Querying purchases...".dLog()
        withContext(Dispatchers.IO) {
            val subPurchaseResultDeffer = async {
                queryPurchasesByType(ProductType.SUBS)
            }
            val iapPurchaseResultDeffer = async {
                queryPurchasesByType(ProductType.INAPP)
            }
            val subPurchaseResult = subPurchaseResultDeffer.await()
            val iapPurchaseResult = iapPurchaseResultDeffer.await()
            // 合并订阅商品和内购商品信息的查询结果。
            val billingResult = combineBillingResult(
                subPurchaseResult.billingResult,
                iapPurchaseResult.billingResult
            )
            val purchaseList = subPurchaseResult.purchasesList + iapPurchaseResult.purchasesList
            handlePurchasesResult(billingResult, purchaseList)
        }
    }

    /**
     * 根据类型查询已拥有商品。
     * @param type 商品类型。
     */
    private suspend fun queryPurchasesByType(@ProductType type: String): PurchasesResult {
        val params = QueryPurchasesParams.newBuilder().setProductType(type)
        return billingClient.queryPurchasesAsync(params.build())
    }

    /**
     * PlayStore 的购买页关闭，回到应用时，常见情况：
     * 1.responseCode == BillingResponseCode.OK 表示完成购买
     * 此时 [purchases] 不会空，但请注意，列表中并非全部已拥有的商品，而是仅包含方才购买的商品。
     *
     * 2.responseCode != BillingResponseCode.OK 取消购买、已拥有该商品等。
     * 此时 [purchases] 为空。
     */
    override fun onPurchasesUpdated(
        billingResult: BillingResult,
        purchases: List<Purchase>?
    ) {
        ListenerHolder.onPurchasesUpdated(billingResult, purchases)
        val responseCode = billingResult.responseCode
        val debugMessage = billingResult.debugMessage
        "onPurchasesUpdated:$responseCode:$debugMessage".dLog()
        when (responseCode) {
            BillingResponseCode.OK -> {
                if (purchases == null) "onPurchasesUpdated: null purchase list".eLog()
            }
            BillingResponseCode.USER_CANCELED -> {
                "onPurchasesUpdated: User canceled the purchase".iLog()
            }
            BillingResponseCode.ITEM_ALREADY_OWNED -> {
                "onPurchasesUpdated: The user already owns this item".iLog()
            }
            BillingResponseCode.DEVELOPER_ERROR -> {
                (
                    "onPurchasesUpdated: Developer error means that Google Play " +
                        "does not recognize the configuration. If you are just getting started, " +
                        "make sure you have configured the application correctly in the " +
                        "Google Play Console. The product ID must match and the APK you " +
                        "are using must be signed with release keys."
                    ).eLog()
            }
        }
    }

    /**
     * 处理已拥有商品查询结果。
     * @param purchaseList 已拥有商品（包括：订阅商品、一次性内购商品，未消耗的消耗性内购商品）。
     */
    private suspend fun handlePurchasesResult(
        billingResult: BillingResult,
        purchaseList: List<Purchase>
    ) {
        ListenerHolder.onQueryPurchasesResponse(billingResult, purchaseList)
        processPurchases(purchaseList)
    }

    /**
     * 查询到已拥有商品，或者已拥有商品更新时，对其进行进一步处理。
     * @param purchasesList 已拥有的商品。
     */
    private suspend fun processPurchases(
        purchasesList: List<Purchase>
    ) {
        val subPurchases = purchasesList.subPurchases()
        val oneTimePurchases = purchasesList.oneTimePurchases()
        val consumablePurchases = purchasesList.consumablePurchases()
        "queryPurchases result: ${subPurchases.size} sub, ${oneTimePurchases.size} one-time iap, ${consumablePurchases.size} consumable iap".dLog()
        DataHolder.resetPurchases(subPurchases + oneTimePurchases)
        // 对订阅商品及一次性内购商品进行确认。
        (subPurchases + oneTimePurchases).forEach {
            if (it.purchaseState == PurchaseState.PURCHASED && !it.isAcknowledged) {
                acknowledgePurchase(
                    it,
                    if (subPurchases.contains(it)) ProductType.SUBS else ProductType.INAPP
                )
            }
        }
        // 对可消耗性商品进行消耗。
        consumablePurchases.forEach {
            if (it.purchaseState == PurchaseState.PURCHASED) {
                consumePurchase(it)
            }
        }
    }

    /**
     * 对商品进行确认（订阅商品或者一次性内购商品）。
     * @param purchase 待确认的商品。
     * @param type 商品类型。
     */
    private suspend fun acknowledgePurchase(
        purchase: Purchase,
        @ProductType type: String
    ) {
        val params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken)
        val result = withContext(Dispatchers.IO) {
            billingClient.acknowledgePurchase(params.build())
        }
        ListenerHolder.onPurchaseConfirmResult(purchase, type.isSub(), isConsumable = false, result)
        "${if (type.isSub()) "acknowledge sub" else "acknowledge one-time iap"}: ${result.responseCode} ${result.debugMessage}".dLog()
    }

    /**
     * 对可消耗性商品进行消耗。
     * @param purchase 待消耗的商品。
     */
    private suspend fun consumePurchase(purchase: Purchase) {
        val consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchase.purchaseToken)
        val result = withContext(Dispatchers.IO) {
            billingClient.consumePurchase(consumeParams.build())
        }.billingResult
        ListenerHolder.onPurchaseConfirmResult(
            purchase,
            isSub = false,
            isConsumable = true,
            result
        )
        "consume purchase: ${result.responseCode} ${result.debugMessage}".dLog()
    }
}
